# 导入必要的库
import os  # 用于处理文件路径
import string  # 用于生成随机字符串
import random  # 用于生成随机数
from io import BytesIO  # 用于在内存中读写二进制数据

# 从Pillow库导入图像处理相关模块
from PIL import Image  # 用于创建和处理图像
from PIL import ImageFilter  # 用于应用图像滤镜
from PIL.ImageDraw import Draw  # 用于在图像上绘图
from PIL.ImageFont import truetype  # 用于加载TrueType字体

# 需要安装Pillow库
# 安装命令: pip install pillow

class Bezier:
    """用于生成贝塞尔曲线的类"""
    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])  # 生成0到1之间的21个t值
        self.beziers = {}  # 用于缓存生成的贝塞尔曲线

    def pascal_row(self, n):
        """计算帕斯卡三角形的第n行"""
        result = [1]  # 第一列的值为1
        x, numerator = 1, n
        for denominator in range(1, n // 2 + 1):
            x *= numerator  # 计算组合数
            x /= denominator
            result.append(x)
            numerator -= 1
        if n & 1 == 0:  # 如果n是偶数
            result.extend(reversed(result[:-1]))
        else:  # 如果n是奇数
            result.extend(reversed(result))
        return result

    def make_bezier(self, n):
        """生成贝塞尔曲线的控制点"""
        try:
            return self.beziers[n]  # 如果已缓存，直接返回
        except KeyError:
            combinations = self.pascal_row(n - 1)  # 计算组合数
            result = []
            for t in self.tsequence:  # 计算贝塞尔曲线的点
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations, tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result  # 缓存计算结果
            return result

class Captcha(object):
    """验证码生成类"""
    def __init__(self):
        self._bezier = Bezier()  # 初始化贝塞尔曲线生成器
        self._dir = os.path.dirname(__file__)  # 获取当前文件所在目录

    @staticmethod
    def instance():
        """单例模式，确保只有一个实例存在"""
        if not hasattr(Captcha, "_instance"):
            Captcha._instance = Captcha()
        return Captcha._instance

    def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
        """初始化验证码的基本属性"""
        self._text = text if text else random.sample(string.ascii_uppercase + '3456789', 4)  # 随机生成4个字符的验证码文本
        self.fonts = fonts if fonts else \
            [os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]  # 设置字体
        self.width = width  # 验证码图片宽度
        self.height = height  # 验证码图片高度
        self._color = color if color else self.random_color(0, 200, random.randint(220, 255))  # 随机颜色

    @staticmethod
    def random_color(start, end, opacity=None):
        """生成随机颜色"""
        red = random.randint(start, end)
        green = random.randint(start, end)
        blue = random.randint(start, end)
        if opacity is None:
            return red, green, blue
        return red, green, blue, opacity

    def background(self, image):
        """绘制验证码背景"""
        Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
        return image

    @staticmethod
    def smooth(image):
        """对图像进行平滑处理"""
        return image.filter(ImageFilter.SMOOTH)

    def curve(self, image, width=4, number=6, color=None):
        """绘制贝塞尔曲线"""
        dx, height = image.size  # 获取图像宽度和高度
        dx /= number  # 平均分割宽度
        path = [(dx * i, random.randint(0, height)) for i in range(1, number)]  # 生成曲线控制点
        bcoefs = self._bezier.make_bezier(number - 1)  # 生成贝塞尔曲线系数
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))  # 计算贝塞尔曲线的点
        Draw(image).line(points, fill=color if color else self._color, width=width)  # 绘制曲线
        return image

    def noise(self, image, number=50, level=2, color=None):
        """在图像上添加噪点"""
        width, height = image.size  # 获取图像宽度和高度
        dx = width / 10
        width -= dx
        dy = height / 10
        height -= dy
        draw = Draw(image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
        return image

    def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        """在图像上绘制验证码文本"""
        color = color if color else self._color  # 文本颜色
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])  # 加载字体
        draw = Draw(image)
        char_images = []
        for c in self._text:  # 为每个字符生成图像
            font = random.choice(fonts)
            c_width, c_height = draw.textbbox((0, 0), c, font=font)[2:]  # 获取字符大小
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)  # 绘制字符
            char_image = char_image.crop(char_image.getbbox())  # 裁剪字符图像
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)  # 计算字符起始位置
        for char_image in char_images:  # 将字符图像粘贴到验证码图像上
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)  # 创建遮罩
            image.paste(char_image,
                        (offset, int((height - c_height) / 2)),
                        mask)
            offset += int(c_width * squeeze_factor)
        return image

    @staticmethod
    def warp(image, dx_factor=0.27, dy_factor=0.21):
        """扭曲图像"""
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        image2 = Image.new('RGB',
                           (width + abs(x1) + abs(x2),
                            height + abs(y1) + abs(y2)))
        image2.paste(image, (abs(x1), abs(y1)))  # 粘贴原图像到新图像
        width2, height2 = image2.size
        return image2.transform(
            (width, height), Image.QUAD,
            (x1, y1,
             -x1, height2 - y2,
             width2 + x2, height2 + y2,
             width2 - x2, -y1))  # 应用扭曲变换

    @staticmethod
    def offset(image, dx_factor=0.1, dy_factor=0.2):
        """偏移图像"""
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        image2 = Image.new('RGB', (width + dx, height + dy))
        image2.paste(image, (dx, dy))  # 偏移原图像
        return image2

    @staticmethod
    def rotate(image, angle=25):
        """旋转图像"""
        return image.rotate(
            random.uniform(-angle, angle), Image.BILINEAR, expand=1)  # 随机角度旋转

    def captcha(self, path=None, fmt='JPEG'):
        """生成验证码图像"""
        image = Image.new('RGB', (self.width, self.height), (255, 255, 255))  # 创建白色背景图像
        image = self.background(image)  # 绘制背景
        image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])  # 绘制文本
        image = self.curve(image)  # 绘制曲线
        image = self.noise(image)  # 添加噪点
        image = self.smooth(image)  # 平滑图像
        name = "".join(random.sample(string.ascii_lowercase + string.ascii_uppercase + '3456789', 24))  # 生成随机文件名
        text = "".join(self._text)  # 获取验证码文本
        out = BytesIO()  # 使用BytesIO代替StringIO
        image.save(out, format=fmt)  # 保存图像到内存
        if path:
            image.save(os.path.join(path, name), fmt)  # 如果提供路径，则保存到文件
        return name, text, out.getvalue()  # 返回文件名、验证码文本和图像数据

    def generate_captcha(self):
        """生成并返回验证码"""
        self.initialize()  # 初始化验证码
        return self.captcha("")  # 生成验证码图像

# 创建Captcha实例
captcha = Captcha.instance()

# 如果脚本直接运行，则生成验证码并打印
if __name__ == '__main__':
    print(captcha.generate_captcha())